package com.zzyl.serve.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.bean.BeanUtils;
import com.zzyl.serve.domain.AlertData;
import com.zzyl.serve.domain.DeviceData;
import com.zzyl.serve.mapper.DeviceMapper;
import com.zzyl.serve.mapper.NursingElderMapper;
import com.zzyl.serve.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.serve.mapper.AlertRuleMapper;
import com.zzyl.serve.domain.AlertRule;
import com.zzyl.serve.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * 报警规则Service业务层处理
 *
 * @author liuyp
 * @date 2025-08-26
 */
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private SysUserRoleMapper userRoleMapper;
    @Autowired
    private NursingElderMapper nursingElderMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return alertRuleMapper.selectById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return alertRuleMapper.insert(alertRule);
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return alertRuleMapper.updateById(alertRule);
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return alertRuleMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return alertRuleMapper.deleteById(id);
    }

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 定时任务处理器  过滤已经处理  保存数据库
     */
    @Override
    public void handleAlertJobs() {
        //判断 alertRule表内数值是否为  启用状态  ，即是报警规则是否为启用，禁用则返回
        Long count = count(Wrappers.<AlertRule>lambdaQuery().eq(AlertRule::getStatus, 1));
        if (count == 0) {
            return;
        }
        //因为设备上传的设备数据在redis中，获取设备上传的数据，判断是否为空
        List<Object> DeviceAMQPdata = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (ObjectUtil.isEmpty(DeviceAMQPdata)) {
            return;
        }

        //将设备数据解析为DeviceData列表
        //现在的数据是json所以不能用java对象去看，  需要转成java对象再用
        for (Object devicekey : DeviceAMQPdata) {
            List<DeviceData> list = JSONUtil.toList(devicekey.toString(), DeviceData.class);
            for (DeviceData deviceData : list) {
                //前置校验  如果这条设备数据已经过去1分钟了，就直接结束（因为定时任务每分钟执行一次，一分钟之前的数据应该是已经被处理过的）
                LocalDateTime alarmTime = deviceData.getAlarmTime();  //设备报警时间
                //判断是否报警时间已经过期1分钟   alarmTime是设备报警时间    ChronoUnit.设置时间单位
                long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
                if (between > 60) {
                    return;
                }

                //判断报警规则 包含的设备  如果为空则退出  如果不为空则继续， 代码健壮性

                List<AlertRule> alertRuleList = list(Wrappers.<AlertRule>lambdaQuery()
                        .eq(AlertRule::getProductKey, deviceData.getProductKey())
                        .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
//                        .eq(AlertRule::getStatus,1)
                        .in(AlertRule::getIotId, "-1", deviceData.getIotId()));


                if (ObjectUtil.isEmpty(alertRuleList)) {
                    return;
                }
                for (AlertRule alertRule : alertRuleList) {

                    questionDeviceDataAndRule(deviceData, alertRule);
                }

            }
        }


    }

    /**
     * 判断每一个上传数据的设备 是否在报警规则的生效范围时间内
     *
     * @param deviceData 设备上传的数据
     * @param alertRule  报警规则表
     */
    private void questionDeviceDataAndRule(DeviceData deviceData, AlertRule alertRule) {
        //1.判断设备上传数据报警时间  是否在报警规则的生效范围时间内
        String period = alertRule.getAlertEffectivePeriod();  //报警规则生效范围时间
        //时间段字符串分割为开始和结束时间
        String[] split = period.split("~");
        LocalTime start = LocalTime.parse(split[0]);  //生效开始时间
        LocalTime end = LocalTime.parse(split[1]);  //生效结束时间
        LocalTime ararmTime = deviceData.getAlarmTime().toLocalTime();  //上传数据的报警时间
        //判断报警时间是否在生效时间内  isbefore（）内的数字 之前是否是ararmtime  是true
        if (ararmTime.isBefore(start) || ararmTime.isAfter(end)) {
            return;
        }

        //2.判断每个设备上传数据 是否达到规则的异常阈值，如果达到去判断异常的沉默时间是否需要报警，未达到异常数据阈值，代表机器正常，所以清理掉异常次数缓存
        //判断设备上传数据  是否达到规则的异常阈值    上传设备的功能名称  去查询数据库的列表  再对比   运算付  阈值值
        String countkey = StringUtils.format(CacheConstants.IOT_ALERT_COUNT_KEY, deviceData.getIotId(), alertRule.getId());
        double value = Double.parseDouble(deviceData.getDataValue());  //（如何保证value值是在同功能名称下）设备上传的数据  value值
        if ("<".equals(alertRule.getOperator()) && value >= alertRule.getValue()
                || ">=".equals(alertRule.getOperator()) && value < alertRule.getValue()) {
            redisTemplate.delete(countkey); //清理异常次数
            return;
        }

        //2.1 异常数据是否需要报警  判断根据：前端发送过来的沉默周期时间内 、（这里需要做redis异常次数加1）  异常次数是否达到周期要求
        //当前是否在沉默周期内？
        String silentFlagKey = StringUtils.format(CacheConstants.IOT_ALERT_SIENT_KEY, deviceData.getIotId(), alertRule.getId());  //获取异常沉默标志
        Boolean isSilent = redisTemplate.hasKey(silentFlagKey);
        if (isSilent) {
            return;
        }

        //异常持续周期次数 +1
        Long count = redisTemplate.opsForValue().increment(countkey, 1);
        //异常次数未达到持续周期要求 是 结束  否 继续
        if (count < alertRule.getDuration()) {
            return;
        }

        //3*.触发警报成功，删除警告触发次数缓存，进入沉默周期（设置沉默周期标识，并设置过期时间为一个沉默周期，过期自动失效，结束沉默周期）
        redisTemplate.delete(countkey);
        redisTemplate.opsForValue().set(silentFlagKey, 1, alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //4.查找报警负责人
        //报警数据类型为老人异常时候
        Set<Long> userIds = new HashSet<>();
        if (alertRule.getAlertDataType() == 1) {
            //设备异常，根据角色去找维修工 sys_user id的集合
            Set<Long> ids = userRoleMapper.seletUserByIdsName("维修工");
            userIds.addAll(ids);
        } else if (alertRule.getAlertDataType() == 0) {
            //老人异常 最终找护理员
            if (deviceData.getLocationType() == 0) {
                //说明是随身设备，  设备绑定的位置是老人的id，可以根据老人id找到护理员id
                Set<Long> ids = nursingElderMapper.selectNurseIdsByElderId(deviceData.getAccessLocation());
                userIds.addAll(ids);
            } else if (deviceData.getLocationType() == 1) {
                //说明是固定设备，  设备绑定的是床位， 通过床位找到老人id，再根据老人找到护理员id
                Set<Long> ids = deviceMapper.slectNurseIdsByBedIotid(deviceData.getIotId());
                userIds.addAll(ids);
            }
        }
        //将超级管理员添加成责任人
        Set<Long> admins = userRoleMapper.seletUserByIdsName("超级管理员");
        userIds.addAll(admins);

        //5.保存报警数据
        String reason = deviceData.getFunctionId() + alertRule.getOperator() + alertRule.getValue() + "持续" + alertRule.getDuration() + "周期";
        ArrayList<AlertData> list = new ArrayList<>();
        for (Long userId : userIds) {
            AlertData alertData = new AlertData();
            BeanUtils.copyProperties(deviceData,alertData);
            alertData.setId(null);

            //补充数据
            alertData.setAlertRuleId(alertRule.getId());
            alertData.setAlertReason(reason);
            alertData.setType(alertRule.getAlertDataType());
            alertData.setStatus(0);
            alertData.setUserId(userId);

            list.add(alertData);
        }
        alertDataService.saveBatch(list);


    }

    /**
     * 处理报警任务
     */
    @Override
    public void handleAlertJob() {
        //*1 先校验所有可能直接结束的规则
        //1.前置校验：如果没有任何已启用的规则，就结束
        Long count = count(Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1));
        if (count == 0) {
            return;
        }

        //2.从redis中获取设备上报的数据，取hash里所有的value值 ：hvals key
        List<Object> dataArrayList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (ObjectUtil.isEmpty(dataArrayList)) {
            return;
        }

        //3.将所有上报的数据转换成list《DeviceData》  并处理每个设备数据
        for (Object dataArrayJson : dataArrayList) {
            List<DeviceData> list = JSONUtil.toList(dataArrayJson.toString(), DeviceData.class);
            for (DeviceData deviceData : list) {
                //将每一条设备数据，交给方法去处理
                handleDeviceData(deviceData);
            }
        }

    }

    /**
     * 处理每一条上报数据 校验是否需要报警
     *
     * @param deviceData redis中json格式的上传数据
     */
    private void handleDeviceData(DeviceData deviceData) {
        //前置校验  如果这条设备数据已经过去1分钟了，就直接结束（因为定时任务每分钟执行一次，一分钟之前的数据应该是已经被处理过的）
        LocalDateTime alarmTime = deviceData.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            return;
        }
        // 查询能够针对此数据生效的报警规则
        LambdaQueryWrapper<AlertRule> wrapper = Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .in(AlertRule::getIotId, "-1", deviceData.getIotId());
        List<AlertRule> alertRuleListlist = list(wrapper);
        if (ObjectUtil.isNotEmpty(alertRuleListlist)) {
            return;
        }

        //遍历每个规则：判断当前这条设备  对  每条规则是否触发报警
        for (AlertRule alertRule : alertRuleListlist) {
            handleDeviceDataAndRule(deviceData, alertRule);
        }
    }

    /**
     * 处理此设备与当前规则的对比判断
     *
     * @param deviceData 设备数据
     * @param alertRule  使用的报警规则
     */
    private void handleDeviceDataAndRule(DeviceData deviceData, AlertRule alertRule) {
        //1.判断这条设备数据上报时间，在不在这个规则的有效时间范围内
        //1.1 获取此规则的有效时间范围
        String period = alertRule.getAlertEffectivePeriod();
        //1.2 把字符串切分成数组，split（“分隔符”）
        String[] split = period.split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end = LocalTime.parse(split[1]);
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        //1.3 如果设备的上报时间早于开始 或 设备上报时间晚于结束时间，都不需要处理
        if (alarmTime.isBefore(start) || alarmTime.isAfter(end)) {
            return;
        }

        //2.如果此设备数据都没有异常，结束
        double value = Double.parseDouble(deviceData.getDataValue());
        String countkey = StringUtils.format(CacheConstants.IOT_ALERT_COUNT_KEY, deviceData.getIotId(), alertRule.getId());
        if ("<".equals(alertRule.getOperator()) && value >= alertRule.getValue()
                || ">=".equals(alertRule.getOperator()) && value < alertRule.getValue()) {
            //没有异常，要清理掉异常次数
            redisTemplate.delete(countkey);
            return;
        }
        //3.如果此设备数据没有达到报警阈值，结束
        //3.1如果当前正处于报警的沉默期间（之前已经通过责任人去处理了），则直接结束
        // 如果最终报警了，通知责任人，那么就要设置一个沉默的标志，有效期是“报警沉默周期”
        //只是查询一下是否有沉默的标志，如果有就直接结束
        //我们将沉默标记存储到Redis里，准备一个key是  设备标识+规则标识，存储值随便 比如“1”
        String silentFlagKey = StringUtils.format(CacheConstants.IOT_ALERT_SIENT_KEY, deviceData.getIotId(), alertRule.getId());
        Boolean isSilent = redisTemplate.hasKey(silentFlagKey);
        if (isSilent) {
            return;
        }

        //3.2 如果异常持续周期 没有达到上限，同样不需要报警，直接结束
        //每次定时任务执行时，发现数据异常，要统计异常次数+1（持续周期+1），持续周期（导演次数）同样存储到Redis里
        //Redis的Key：包含设备id+规则id value值：异常的次数， redis的string命令，数字值自增1：incr key
        Long count = redisTemplate.opsForValue().increment(countkey, 1);
        if (count < alertRule.getDuration()) {
            return;
        }

        //触发警报成功，删除警告触发次数缓存，进入沉默周期（设置沉默周期标识，并设置过期时间为一个沉默周期，过期自动失效，结束沉默周期）
        redisTemplate.delete(countkey);
        redisTemplate.opsForValue().set(silentFlagKey, 1, alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //4.查找责任人
        Set<Long> userIds = new HashSet<>();
        if (alertRule.getAlertDataType() == 1) {
            //是设备异常，要根据角色  去找维修工查找用户id集合
            Set<Long> ids = userRoleMapper.seletUserByIdsName("维修工");
            userIds.addAll(ids);
        } else if (alertRule.getAlertDataType() == 0) {
            //老人异常，最终找护理员，但是表逻辑是
            if (deviceData.getLocationType() == 0) {
                //说明是随身设备，设备绑定的位置是老人的id，可以根据老人id找到护理员id
                Set<Long> ids = nursingElderMapper.selectNurseIdsByElderId(deviceData.getAccessLocation());
                userIds.addAll(ids);
            } else if (deviceData.getLocationType() == 1) {
                //是固定设备，设备绑定的是床位，要通过床位找到老人id，再根据老人id找到护理人元id
                Set<Long> ids = deviceMapper.slectNurseIdsByBedIotid(deviceData.getIotId());
                userIds.addAll(ids);
            }
        }
        //将超级管理员添加成责任人
        Set<Long> admins = userRoleMapper.seletUserByIdsName("超级管理员");
        userIds.addAll(admins);

        //5.保存报警数据
        String reason = deviceData.getFunctionId() + alertRule.getOperator() + alertRule.getValue() + "持续" + alertRule.getDuration() + "周期";

        ArrayList<AlertData> list = new ArrayList<>();
        for (Long userId : userIds) {
            AlertData alertData = new AlertData();
            BeanUtils.copyProperties(deviceData, alertData);

            alertData.setId(null);

            //补充数据
            alertData.setAlertRuleId(alertRule.getId());
            alertData.setAlertReason(reason);
            alertData.setType(alertRule.getAlertDataType());
            alertData.setStatus(0);
            alertData.setUserId(userId);

            list.add(alertData);
        }

        //批量保存
        alertDataService.saveBatch(list);

    }

}



